package transcoding

import java.io.FileOutputStream
import java.io.InputStream
import java.lang.{ Double => JDouble }
import java.lang.{ Long => JLong }
import java.util.UUID
import scala.actors.Actor
import play.api.mvc.Request
import play.api.Logger
import play.api.mvc.AnyContent
import com.xuggle.xuggler.IContainerFormat
import collection.mutable.ListBuffer

class TranscodedStreamWriter(val outStream: FileOutputStream) extends Actor {
  def act() = {
    loop {
      react {
        case ('Write, data: Array[Byte]) =>
          outStream.write(data)
        case ('Takeover, trasncodedIS: TranscodedInputStream, procIS: InputStream) =>
          def readStream(): Unit = {
            val buffer = new Array[Byte](1024 * 8)
            procIS.read(buffer) match {
              case -1 =>
                //trasncodedIS.closeTrascoding
                outStream.flush
                exit
              case read =>
                val output = new Array[Byte](read)
                System.arraycopy(buffer, 0, output, 0, read)
                outStream.write(output)

                readStream
            }
          }
          readStream
      }
    }
  }
}

class TranscodedStreamConsumer() extends Actor {
  val buffer = new Array[Byte](1024 * 8)
  def act() = {
    loop {
      react {
        case ('Consume, trasncodedIS: TranscodedInputStream, procIS: InputStream) =>
          def readStream(): Unit = {
            procIS.read(buffer) match {
              case -1 =>
                //trasncodedIS.closeTrascoding
                exit
              case read =>
                readStream
            }
          }
          readStream
      }
    }
  }
}

class StreamGobbler extends Actor {

  val streams: scala.collection.mutable.Buffer[InputStream] =  new ListBuffer[InputStream];
  val buf = new Array[Byte](1024)
  def gobble: Unit = {
    Logger.debug("stream size %d, on thread %s".format(this.streams.size, Thread.currentThread().getName))
    streams.foreach {
      str =>
        try {
          val available: Int = str.available()
          if (available > 0) {
            Logger.debug("Gobbling stream " + str.toString)
            Logger.warn("Read %d bytes" format str.read(buf))
          }
        }
        catch{
          case error =>
            TranscoderUtil.closeQuietly(str)
            streams -= str
        }
    };

    if (streams.size > 0){
      Thread.sleep(5000)
      this ! 'Gobble
    }
  }

  def act = {
    loop {
      react {
        case 'Gobble =>
          if (this.sender != this)
            Logger.error("sender=%s, this=%s".format(sender, this))
          else gobble
        case ('AddStream, stream: InputStream) =>
          Logger.debug("[%s]Adding stream %s for gobbling...".format(this.toString, stream.toString))
          streams += stream
          Logger.debug("Streams size = " +streams.size)
          gobble

        /*case (_, stream: InputStream) =>
          Logger.debug("[%s]Gobbling standard error...".format(this.toString))

          var reader: BufferedReader = null
          try {
            reader = new BufferedReader(new InputStreamReader(stream))

            def read: Unit = {
              val line = reader.readLine()
              if (line != null) {
                Logger.debug(line)
                read
              }
            }
            read
          } catch {
            case error => Logger.error("Error while gobbling standard error", error)
          } finally {
            TranscoderUtil.closeQuietly(reader)
            TranscoderUtil.closeQuietly(stream)
          }*/
        case message => Logger.error("StreamGobbler could not undserstand message: %s" format message)
      }
    }
  }
}

object TranscoderUtil {
  import java.lang.{ Long => JLong }
  import java.lang.{ Double => JDouble }
  import java.lang.{ Integer => JInt }

  private def offsetSeconds(params: Map[String, Seq[String]]) = {
    val startParam = params.getOrElse("start", Seq("0")).fold("")((a, b) => a + b);
    try { JDouble.parseDouble(startParam) }
    catch { case e => Logger.warn("Could not parse start param: %s".format(startParam), e); 0.0 }
  }

  private def offsetFromSecondsToBytes(params: Map[String, Seq[String]])(implicit mf: MediaFile) = {
    (offsetSeconds(params) * (mf.bitRate.get / 8)).round
  }

  private def bitrate(params: Map[String, Seq[String]])(implicit mf: MediaFile) = {
    if (params.contains("maxBitRate")) {
      try {
        val bitrate = params.getOrElse("maxBitRate", Seq()).fold("")((a, b) => a + b)
        val bitRateValue = JInt.parseInt(bitrate)
        if (bitRateValue > 0) Some(bitRateValue) else None
      } catch {
        case _ => None
      }
    } else None
  }

  def size(bitrate: Option[Int])(implicit mf: MediaFile) = {

    var scalingRatio = 1.0
    bitrate match {
      case None =>
      case Some(br) =>
        Logger.debug("scala.math.sqrt(%d / %d)".format(br, mf.bitRate.get))
        if (mf.bitRate.getOrElse(0) > 0 && br > 0)
          scalingRatio = scala.math.sqrt(br / mf.bitRate.get)
        else Logger.warn("Media File contained negative bitrate")

        if (scalingRatio < 1.0) {
          //put a lower bound
          scalingRatio = scala.math.max(scalingRatio, 0.33);
          Logger.debug("Scaling video dimension by %f becasue max bitrate is %s".format(scalingRatio, br))
        }
    }

    "%dx%d".format(even((mf.videoDimensions(0) * scalingRatio).round), even((mf.videoDimensions(1) * scalingRatio).round))
  }

  private def even(i: Long) = {
    i + (i % 2)
  }

  def apply(filePath: String, params: Map[String, Seq[String]], reqID: String, flag: Boolean) = {

  }

  def apply(mfm: MediaFileManager, request: Request[AnyContent], reqID: String): Option[(InputStream, MediaFile)] = {

    implicit val mediaFile = mfm.mainMediaFile
    val params = request.queryString
    val skipTo = offsetSeconds(params)
    val maxBitRate = bitrate(params)
    val player = params.getOrElse("playerID", Seq()).fold("")((a, b) => a + b)

    val pi = new ProcessInfo(UUID.randomUUID.toString, request.host, mfm, FlashPlayer)

    Logger.debug("Process Info " + pi)

    TranscodeProcessManager.startProcessAndContructStream(pi, mfm, maxBitRate, None, skipTo)
      .map(p => (p._1, p._2))
  }

  def closeQuietly(in: java.io.Closeable) {
    try {
      Logger.trace("Quietly closing %s".format(in))
      in.close()
    } catch {
      case e => Logger.warn("Error while quietly closing", e)
    }
  }

  def forceSkip(fileStream: InputStream, start: Long): InputStream = {
    var toSkip = start
    var tries = 0
    Logger.debug("Skipping %d bytes".format(start))
    while (toSkip > 0) {
      val skipped = fileStream.skip(start)
      Logger.debug("Skipped %d bytes".format(skipped))
      toSkip -= skipped
      if (tries >= 9) {
        Logger.warn("After 10 tries it is not possible to fully skip. Skipped %d".format(start - toSkip))
        return fileStream
      }
      tries += 1
    }
    fileStream
  }

  def encodeSpaces(str: String) = { str.replaceAll(" ", "%20%") }
  def decodeSpaces(str: String) = { str.replaceAll("%20%", " ") }
  def ensureDirExists(filepath: String) = {
    val file = new java.io.File(filepath)
    file.getParentFile().mkdirs()

    filepath
  }
  def getXuggleFormat(format: String) = {
    val contFormat = IContainerFormat.make()
    contFormat.setInputFormat(format);

    contFormat
  }
  def getXuggleFormat(maybeFormat: Option[String]) = {
    maybeFormat.map(
      format =>
        {
          val contFormat = IContainerFormat.make()
          contFormat.setInputFormat(format);

          contFormat
        })
  }
}
